<?php

namespace Yj\Crontab;

use App\Dao\Crontab\CrontabLogDao;
use App\Model\Crontab\CrontabLog;
use Carbon\Carbon;
use Hyperf\Contract\StdoutLoggerInterface;

use Hyperf\Crontab\LoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Utils\Coroutine;
use Psr\Container\ContainerInterface;
use Swoole\Timer;

class Executor
{
    /**
     * @var ContainerInterface
     */
    protected ContainerInterface $container;

    /**
     * @param ContainerInterface $container
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        if ($container->has(LoggerInterface::class)) {
            $this->logger = $container->get(LoggerInterface::class);
        } elseif ($container->has(StdoutLoggerInterface::class)) {
            $this->logger = $container->get(StdoutLoggerInterface::class);
        }
    }

    public function execute(Crontab $crontab){
        if (! $crontab instanceof Crontab || ! $crontab->getExecuteTime()) {
            return;
        }

        $diff = $crontab->getExecuteTime()->diffInRealSeconds(new Carbon());
        $callback = null;
        switch ($crontab->getType()) {
            case 'callback':
                [$class,$method] = explode(',',$crontab->getCallback());
                $parameters =  null;
                if ($class && class_exists($class) && method_exists($class, $method)) {
                    $callback = function () use ($class, $method, $parameters, $crontab) {
                        $runnable = function () use ($class, $method, $parameters, $crontab) {
                            try {
                                $result = true;
                                $res = null;
                                $instance = make($class);
                                if (!empty($parameters)) {
                                    $res = $instance->{$method}($parameters);
                                } else {
                                    $res = $instance->{$method}();
                                }
                            } catch (\Throwable $throwable) {
                                $result = false;
                            } finally {
                                $this->logResult($crontab, $result, isset($throwable) ? $throwable->getMessage() : $res);
                            }
                        };
                        Coroutine::create($this->decorateRunnable($crontab, $runnable));
                    };
                }
                break;
            case SettingCrontab::COMMAND_CRONTAB:
                $command = ['command' => $crontab->getCallback()];
                $parameter = $crontab->getParameter() ?: '{}';
                $input = make(ArrayInput::class, array_merge($command, json_decode($parameter, true)));
                $output = make(NullOutput::class);
                $application = $this->container->get(ApplicationInterface::class);
                $application->setAutoExit(false);
                $callback = function () use ($application, $input, $output, $crontab) {
                    $runnable = function () use ($application, $input, $output, $crontab) {
                        $result = $application->run($input, $output);
                        $this->logResult($crontab, $result === 0, $result);
                    };
                    $this->decorateRunnable($crontab, $runnable)();
                };
                break;
            case SettingCrontab::URL_CRONTAB:
                $clientFactory = $this->container->get(ClientFactory::class);
                $client = $clientFactory->create();
                $callback = function () use ($client, $crontab) {
                    $runnable = function () use ($client, $crontab) {
                        try {
                            $response = $client->get($crontab->getCallback());
                            $result = $response->getStatusCode() === 200;
                        } catch (\Throwable $throwable) {
                            $result = false;
                        }
                        $this->logResult(
                            $crontab,
                            $result,
                            (!$result && isset($response)) ? $response->getBody() : ''
                        );
                    };
                    $this->decorateRunnable($crontab, $runnable)();
                };
                break;
            case SettingCrontab::EVAL_CRONTAB:
                $callback = function () use ($crontab) {
                    $runnable = function () use ($crontab) {
                        $result = true;
                        try {
                            eval($crontab->getCallback());
                        } catch (\Throwable $throwable) {
                            $result = false;
                        }
                        $this->logResult($crontab, $result, isset($throwable) ? $throwable->getMessage() : '');
                    };
                    $this->decorateRunnable($crontab, $runnable)();
                };
                break;
        }
        $callback && Timer::after($diff > 0 ? $diff * 1000 : 1, $callback);

        return true;
    }

    /**
     * @Inject()
     * @var CrontabLogDao
     */
    protected $crontabLogDao;


    public function logResult(Crontab $crontab, bool $isSuccess, $result = ''){
//        if ($isSuccess) {
//            dump(sprintf('Crontab task [%s] executed successfully at %s.', $crontab->getName(), date('Y-m-d H:i:s')));
//        } else {
//            dump(sprintf('Crontab task [%s] failed execution at %s.', $crontab->getName(), date('Y-m-d H:i:s')));
//        }
//        $this->crontabLogDao->create([
//            'crontab_id'=>$crontab->getCrontabId(),
//            'name'=>$crontab->getName(),
//            'type'=>$crontab->getType(),
//            'rule'=>$crontab->getRule(),
//            'target'=>$crontab->getCallback(),
//            'status'=>$isSuccess?1:0,
//            'exception_info'=>$result,
//        ]);
        
    }

    private function decorateRunnable(Crontab $crontab, \Closure $runnable)
    {
//        if ($crontab->isSingleton()) {
//            $runnable = $this->runInSingleton($crontab, $runnable);
//        }
//
//        if ($crontab->isOnOneServer()) {
//            $runnable = $this->runOnOneServer($crontab, $runnable);
//        }
        return $runnable;
    }




}